#ifndef NOTIFY_H_
#define NOTIFY_H_

#include <gtkmm.h>
#include <map>

#include "conf/element.h"
#include "misc.h"

namespace gpyp {

class Application;

class NotifierBase : boost::noncopyable {
private:
  sigc::signal<void> signal_closed_;
public:
  inline sigc::signal<void> signal_closed() {
    return signal_closed_;
  }

  enum Urgency {
    URGENCY_LOW      = 0,
    URGENCY_NORMAL   = 1,
    URGENCY_CRITICAL = 2,
  };

protected:
  inline const char *normalize_message(const Glib::ustring &message) {
    const char *msg_c = message.c_str();
    for( ; *msg_c == '\n'; ++msg_c) ;
    return msg_c;
  }
  virtual void on_notify(const Glib::ustring &message) = 0;
  virtual void on_show() = 0;
  virtual void on_close() = 0;

  inline virtual void on_closed_event() {
    signal_closed_();
  }

  virtual void on_set_timeout(int timeout) = 0;
  virtual void on_set_urgency(Urgency urgency) = 0;

public:
  inline void notify(const Glib::ustring &message) {
    on_notify(message);
  }
  inline void show() {
    on_show();
  }
  inline void close() {
    on_close();
  }

  inline void set_timeout(int timeout) {
    on_set_timeout(timeout);
  }
  inline void set_urgency(Urgency urgency) {
    on_set_urgency(urgency);
  }

protected:
  inline NotifierBase() { }
public:
  inline virtual ~NotifierBase() { }
};

class UpdatableNotifierBase : public NotifierBase {
private:
  Glib::ustring message_;

protected:
  inline virtual void on_notify(const Glib::ustring &message) {
    const char *msg_c = NULL;;
    if (message_.empty()) {
      msg_c = normalize_message(message);
    } else {
      msg_c = message.c_str();
      message_.append("\n");
    }
    message_.append(msg_c);
    on_update(message_);
  }
  virtual void on_update(const Glib::ustring &message) = 0;

  inline virtual void on_closed_event() {
    message_.clear();
    NotifierBase::on_closed_event();
  }

  inline const Glib::ustring &get_message() const {
    return message_;
  }

protected:
  inline UpdatableNotifierBase() { }
public:
  inline virtual ~UpdatableNotifierBase() { }
};

class NotifyProviderBase : public conf::Element{
public:
  enum Flags {
    F_NONE        = 0,
    F_MARKUP      = 1 << 0,
    F_TIMEOUT     = 1 << 1,
    F_STATUS_ICON = 1 << 2
  };

  enum Type {
    TYPE_NEW    = 0,
    TYPE_CHANGE = 1,
    TYPE_ERROR  = 2,
  };

private:
  RefAttributeFormat format_new_;
  RefAttributeFormat format_change_;

  Flags flags_;

  NotifierBase *notifier_new_;
  NotifierBase *notifier_change_;
  NotifierBase *notifier_error_;

protected:
  inline virtual void on_format(RefChannel ch, Type type, Glib::ustring &message) {
    switch(type) {
    case TYPE_NEW:    format_new_   ->format(message, ch, true); break;
    case TYPE_CHANGE: format_change_->format(message, ch, true); break;
    default: message.clear();
    }
  }

public:
  inline RefAttributeFormat format_new() {
    return format_new_;
  }
  inline RefAttributeFormat format_change() {
    return format_change_;
  }

  inline void notify(RefChannel ch, Type type, bool show) {
    NotifierBase *n = notifier_new_;
    if (type == TYPE_CHANGE && notifier_change_) n = notifier_change_;

    Glib::ustring message;
    on_format(ch, type, message);
    n->notify(message);
    if (show) n->show();
  }
  inline void notify_error(const Glib::ustring &message) {
    notifier_error_->notify(message);
    notifier_error_->show();
  }

#define RUN_NOTIFIERS(R) {                      \
    notifier_new_->R;                           \
    if (notifier_change_) notifier_change_->R;  \
  }
  inline void set_timeout(int timeout) {
    RUN_NOTIFIERS(set_timeout(timeout));
  }

  inline void show() {
    RUN_NOTIFIERS(show());
  }
  inline void close() {
    RUN_NOTIFIERS(close());
  }
#undef RUN_NOTIFIERS

  inline bool has_markup_support() const {
    return (flags_ & F_MARKUP) != 0;
  }
  inline bool has_timeout_support() const {
    return (flags_ & F_TIMEOUT) != 0;
  }
  inline bool has_status_icon_support() const {
    return (flags_ & F_STATUS_ICON) != 0;
  }

protected:
  inline NotifyProviderBase(Application         &app,
                            const Glib::ustring &name,
                            const Glib::ustring &format_new,
                            const Glib::ustring &format_change,
                            Flags                flags,
                            NotifierBase        *notifier_new,
                            NotifierBase        *notifier2,
                            NotifierBase        *notifier3 = NULL) :
    conf::Element   (name),
    format_new_     ("format_new",    format_new,    app),
    format_change_  ("format_change", format_change, app),
    flags_          (flags),
    notifier_new_   (notifier_new),
    notifier_change_(notifier3 ? notifier2 : NULL),
    notifier_error_ (notifier3 ? notifier3 : notifier2)
  {
    add(format_new_);
    add(format_change_);

    notifier_new_->set_urgency(NotifierBase::URGENCY_LOW);
    if (notifier_change_) notifier_change_->set_urgency(NotifierBase::URGENCY_LOW);
    notifier_error_->set_timeout(3000);
    notifier_error_->set_urgency(NotifierBase::URGENCY_CRITICAL);
  }
public:
  inline virtual ~NotifyProviderBase() {
    delete notifier_new_;
    if (notifier_change_) delete notifier_change_;
    delete notifier_error_;
  }
};
typedef boost::shared_ptr<NotifyProviderBase> RefNotifyProviderBase;
ENUM_FLAG_OPERATORS(NotifyProviderBase::Flags);

class Notify : public conf::Element {
public:
  typedef std::list<RefNotifyProviderBase> ProviderList;

  static void init();
  static void uninit();

private:
  conf::RefAttributeBool   notify_error_;
  conf::RefAttributeUint   timeout_;
  conf::RefAttributeStr    notify_sound_;
  conf::RefAttributeDouble notify_sound_volume_;

  ProviderList             providers_;
  RefNotifyProviderBase    provider_;

private:
  inline void timeout_on_changed() {
    if (provider_) provider_->set_timeout(*timeout_ * 1000);
  }

public:
  inline conf::RefAttributeBool notify_error() {
    return notify_error_;
  }
  inline conf::RefAttributeUint timeout() {
    return timeout_;
  }
  inline conf::RefAttributeStr notify_sound() {
    return notify_sound_;
  }
  inline conf::RefAttributeDouble notify_sound_volume() {
    return notify_sound_volume_;
  }

  inline void notify_new(RefChannel ch, bool show = false) {
    if (provider_) provider_->notify(ch, NotifyProviderBase::TYPE_NEW, show);
  }
  inline void notify_change(RefChannel ch, bool show = false) {
    if (provider_) provider_->notify(ch, NotifyProviderBase::TYPE_CHANGE, show);
  }
  inline void notify_error(const char *markup, const char *plain, ...) {
    if (*notify_error_ && provider_) {
      va_list args;
      gchar  *res;
      va_start(args, plain);
      res = g_strdup_vprintf(provider_->has_markup_support() ? markup : plain, args);
      va_end(args);
      provider_->notify_error(res);
      g_free(res);
    }
  }

  inline void show() {
    if (provider_) provider_->show();
  }
  inline void close() {
    if (provider_) provider_->close();
  }

  const ProviderList &get_providers() const {
    return providers_;
  }
  RefNotifyProviderBase get_provider() const {
    return provider_;
  }

  Notify(Application &app, RefStatusIcon status_icon = RefStatusIcon());
};
typedef boost::shared_ptr<Notify> RefNotify;

}/*gpyp*/

#endif/*NOTIFY_H_*/
